using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Practices.CompositeUI.SmartParts;
using Microsoft.Practices.CompositeUI.Utility;

namespace SCSFContrib.CompositeUI.WPF.Workspaces
{
	/// <summary>
	/// Implements an <see cref="IWorkspace"/> that displays smart parts in named zones.
	/// </summary>
	/// <remarks>
	/// <para>
	/// The <c>ZoneWorkspace</c> displays smart parts with in named zones. Instances of <see cref="ZoneSmartPartInfo"/> are used
	/// to specify the name of the zone in which a smart part should be displayed. If no name is given, a default zone is used
	/// (see <see cref="DefaultZone"/>).
	/// </para>
	/// <para>
	/// Zones can be named via the <c>ZoneName</c> attached property. The only requirement of zones is that they be (or inherit
	/// from) <see cref="ContentControl"/>s. Once a zone is added to a <c>ZoneWorkspace</c>, the <c>Workspace</c> attached property
	/// for the zone will yield the <c>ZoneWorkspace</c> instance to which it belongs.
	/// </para>
	/// </remarks>
	/// <example>
	/// The following example shows how a <c>ZoneWorkspace</c> can be set up in XAML:
	/// <code>
	/// <![CDATA[
	///	<cab:ZoneWorkspace x:Name="_zoneWorkspace" DefaultZone="{Binding ElementName=_contentArea}">
	///		<DockPanel>
	///			<ContentControl cab:ZoneWorkspace.ZoneName="Header" DockPanel.Dock="Top"/>
	///			<ContentControl cab:ZoneWorkspace.ZoneName="Footer" DockPanel.Dock="Bottom"/>
	///			<ContentControl x:Name="_contentArea"/>
	///		</DockPanel>
	///	</cab:ZoneWorkspace>
	/// ]]>
	/// </code>
	/// </example>
	/// <example>
	/// The following example shows how a <c>ZoneWorkspace</c> can be set up in code:
	/// <code>
	/// _zoneWorkspace.DefaultZone = _contentArea;
	/// ZoneWorkspace.SetZoneName(_header, "Header");
	/// ZoneWorkspace.SetZoneName(_footer, "Footer");
	/// </code>
	/// </example>
	/// <example>
	/// The following example shows how smart parts can be manipulated in a <c>ZoneWorkspace</c>:
	/// <code>
	/// //show the header smart part in the Header zone
	/// _zoneWorkspace.Show(headerSmartPart, new ZoneSmartPartInfo("Header"));
	/// 
	/// //show the footer smart part in the Footer zone
	/// _zoneWorkspace.Show(footerSmartPart, new ZoneSmartPartInfo("Footer"));
	/// 
	/// //show a smart part in the default zone
	/// _zoneWorkspace.Show(someSmartPart);
	/// </code>
	/// </example>
	public class ZoneWorkspace : ContentControlWorkspace<object, ZoneSmartPartInfo>
	{
		private readonly IDictionary<string, ContentControl> _zones;
		private readonly IDictionary<object, string> _smartPartZoneNames;

		private static readonly DependencyPropertyKey _workspacePropertyKey = DependencyProperty.RegisterAttachedReadOnly("Workspace",
			typeof(ZoneWorkspace),
			typeof(ZoneWorkspace),
			new FrameworkPropertyMetadata());

		/// <summary>
		/// Identifies the <see cref="DefaultZone"/> dependency property.
		/// </summary>
		public static readonly DependencyProperty DefaultZoneProperty = DependencyProperty.Register("DefaultZone",
			typeof(ContentControl),
			typeof(ZoneWorkspace),
			new FrameworkPropertyMetadata(DefaultZoneChanged));
		
		/// <summary>
		/// Identifies the <c>ZoneName</c> attached property.
		/// </summary>
		public static readonly DependencyProperty ZoneNameProperty = DependencyProperty.RegisterAttached("ZoneName",
			typeof(string),
			typeof(ZoneWorkspace),
			new FrameworkPropertyMetadata(ZoneNameChanged));

		/// <summary>
		/// Identifies the <c>Workspace</c> attached property.
		/// </summary>
		public static readonly DependencyProperty WorkspaceProperty = _workspacePropertyKey.DependencyProperty;

		/// <summary>
		/// Gets a read-only dictionary of all the zones that have been configured for this <c>ZoneWorkspace</c>, keyed by their
		/// names.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This property gets a snapshot of all configured zones in this <c>ZoneWorkspace</c>, excluding the
		/// <see cref="DefaultZone"/>.
		/// </para>
		/// </remarks>
		public ReadOnlyDictionary<string, ContentControl> Zones
		{
			get
			{
				return new ReadOnlyDictionary<string,ContentControl>(_zones);
			}
		}

		/// <summary>
		/// Gets or sets the default zone for this <c>ZoneWorkspace</c>.
		/// </summary>
		/// <remarks>
		/// <para>
		/// If no zone name is specified when displaying a smart part, this default zone is used to host the smart part. If no
		/// default zone has been set, an exception will be thrown.
		/// </para>
		/// </remarks>
		public ContentControl DefaultZone
		{
			get
			{
				return GetValue(DefaultZoneProperty) as ContentControl;
			}
			set
			{
				SetValue(DefaultZoneProperty, value);
			}
		}

		/// <summary>
		/// Constructs an instance of <c>ZoneWorkspace</c>.
		/// </summary>
		public ZoneWorkspace()
		{
			_zones = new Dictionary<string, ContentControl>();
			_smartPartZoneNames = new Dictionary<object, string>();
		}

		/// <summary>
		/// Gets the zone name that has been assigned to <paramref name="item"/>.
		/// </summary>
		/// <param name="zone">
		/// The zone whose name is to be retrieved.
		/// </param>
		/// <returns>
		/// The name of the zone, or <see langword="null"/> if no zone name has been assigned.
		/// </returns>
		public static string GetZoneName(ContentControl zone)
		{
			Guard.ArgumentNotNull(zone, "zone");
			return zone.GetValue(ZoneNameProperty) as string;
		}

		/// <summary>
		/// Sets the zone name for <paramref name="zone"/>.
		/// </summary>
		/// <param name="zone">
		/// The zone whose name is to be set.
		/// </param>
		/// <param name="zoneName">
		/// The name to assign to <paramref name="zone"/>.
		/// </param>
		public static void SetZoneName(ContentControl zone, string zoneName)
		{
			Guard.ArgumentNotNull(zone, "zone");
			zone.SetValue(ZoneNameProperty, zoneName);
		}

		/// <summary>
		/// Gets the <c>ZoneWorkspace</c> to which the specified zone belongs.
		/// </summary>
		/// <param name="zone">
		/// The zone whose <c>ZoneWorkspace</c> is to be retrieved.
		/// </param>
		/// <returns>
		/// The <c>ZoneWorkspace</c> to which <paramref name="zone"/> belongs, or <see langword="null"/> if <paramref name="zone"/>
		/// does not belong to a <c>ZoneWorkspace</c>.
		/// </returns>
		public static ZoneWorkspace GetWorkspace(ContentControl zone)
		{
			Guard.ArgumentNotNull(zone, "zone");
			return zone.GetValue(WorkspaceProperty) as ZoneWorkspace;
		}

		/// <summary>
		/// Applies the specified smart part info to the smart part in this workspace.
		/// </summary>
		/// <param name="smartPart">
		/// The smart part to which <paramref name="smartPartInfo"/> will be applied.
		/// </param>
		/// <param name="smartPartInfo">
		/// The <see cref="ZoneSmartPartInfo"/> instance to apply to <paramref name="smartPart"/>.
		/// </param>
		public override void OnApplySmartPartInfo(object smartPart, ZoneSmartPartInfo smartPartInfo)
		{
			OnShow(smartPart, smartPartInfo);
		}

		/// <summary>
		/// Shows the specified smart part.
		/// </summary>
		/// <param name="smartPart">
		/// The smart part to show.
		/// </param>
		/// <param name="smartPartInfo">
		/// The instance of <see name="ZoneSmartPartInfo"/> that provides any extraneous information to be used in the displaying of
		/// <paramref name="smartPart"/>.
		/// </param>
		public override void OnShow(object smartPart, ZoneSmartPartInfo smartPartInfo)
		{
			Guard.ArgumentNotNull(smartPart, "smartPart");
			Guard.ArgumentNotNull(smartPartInfo, "smartPartInfo");
			ContentControl zone = DefaultZone;

			if (!string.IsNullOrEmpty(smartPartInfo.ZoneName))
			{
				if (!_zones.TryGetValue(smartPartInfo.ZoneName, out zone))
				{
					throw new InvalidOperationException(string.Format("Zone '{0}' was not found in this ZoneWorkspace.", smartPartInfo.ZoneName));
				}
			}

			if (zone == null)
			{
				throw new InvalidOperationException("No zone name was specified and no default zone has been configured. You must either specify the zone name or assign a default zone to the ZoneWorkspace.");
			}

			ContentControl oldZone = null;

			if (_smartPartZoneNames.ContainsKey(smartPart))
			{
				oldZone = GetZone(smartPart);
			}

			_smartPartZoneNames[smartPart] = string.IsNullOrEmpty(smartPartInfo.ZoneName) ? null : smartPartInfo.ZoneName;

			if ((oldZone != null) && !object.ReferenceEquals(zone, oldZone))
			{
				//moving smart part to new zone
				UpdateContent(oldZone, UpdateReason.Other);
			}

			UpdateContent(zone, UpdateReason.Other);
			Activate(smartPart);
		}

		/// <summary>
		/// Activates the specified smart part in this workspace.
		/// </summary>
		/// <param name="smartPart">
		/// The smart part to activate.
		/// </param>
		public override void OnActivate(object smartPart)
		{
			Guard.ArgumentNotNull(smartPart, "smartPart");

			if (!InnerSmartParts.Contains(smartPart))
			{
				throw new ArgumentException("smartPart is not present in this workspace.", "smartPart");
			}

			MoveSmartPartInStack(smartPart, StackLocation.TopOfStack);
			ContentControl zone = GetZone(smartPart);
			UpdateContent(zone, UpdateReason.Other);
			zone.Focus();
		}

		/// <summary>
		/// Hides the specified smart part.
		/// </summary>
		/// <param name="smartPart">
		/// The smart part in this workspace to be hidden.
		/// </param>
		public override void OnHide(object smartPart)
		{
			Guard.ArgumentNotNull(smartPart, "smartPart");

			if (!InnerSmartParts.Contains(smartPart))
			{
				throw new ArgumentException("smartPart is not present in this workspace.", "smartPart");
			}

			MoveSmartPartInStack(smartPart, StackLocation.BottomOfStack);
			ContentControl zone = GetZone(smartPart);
			UpdateContent(zone, UpdateReason.Other);
		}

		/// <summary>
		/// Closes the specified smart part.
		/// </summary>
		/// <param name="smartPart">
		/// The smart part in this workspace that should be closed.
		/// </param>
		public override void OnClose(object smartPart)
		{
			Guard.ArgumentNotNull(smartPart, "smartPart");

			if (!InnerSmartParts.Contains(smartPart))
			{
				throw new ArgumentException("smartPart is not present in this workspace.", "smartPart");
			}

			MoveSmartPartInStack(smartPart, StackLocation.BottomOfStack);
			ContentControl zone = GetZone(smartPart);
			object content = UpdateContent(zone, UpdateReason.SmartPartClosed);

			if (content != null)
			{
				//there is still at least one smart part in the workspace so make sure it is activated (the top-most one that is)
				Activate(content);
			}

			_smartPartZoneNames.Remove(smartPart);
		}

		private static void DefaultZoneChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
		{
			ContentControl oldDefaultZone = e.OldValue as ContentControl;
			ContentControl newDefaultZone = e.NewValue as ContentControl;

			if (oldDefaultZone != null)
			{
				DisownZone(oldDefaultZone);
			}

			if (newDefaultZone != null)
			{
				AdoptZone(newDefaultZone);
			}
		}

		private static void ZoneNameChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
		{
			Debug.Assert(dependencyObject is ContentControl);
			Debug.Assert((e.OldValue == null) || (e.OldValue is string));
			Debug.Assert((e.NewValue == null) || (e.NewValue is string));

			ContentControl zone = dependencyObject as ContentControl;
			string oldZoneName = e.OldValue as string;
			string newZoneName = e.NewValue as string;

			if (oldZoneName != null)
			{
				ZoneWorkspace zoneWorkspace = GetWorkspace(zone);
				zoneWorkspace._zones.Remove(oldZoneName);
			}

			if (newZoneName != null)
			{
				ZoneWorkspace zoneWorkspace = AdoptZone(zone);
				zoneWorkspace._zones[newZoneName] = zone;
			}
			else
			{
				DisownZone(zone);
			}
		}

		private static ZoneWorkspace AdoptZone(ContentControl zone)
		{
			Debug.Assert(zone != null);
			DependencyObject parent = zone.Parent;

			while (parent != null && !(parent is IWorkspace))
			{
				parent = LogicalTreeHelper.GetParent(parent);
			}

			ZoneWorkspace zoneWorkspace = parent as ZoneWorkspace;

			if (zoneWorkspace == null)
			{
				throw new InvalidOperationException("Specified zone does not reside in a ZoneWorkspace.");
			}

			zone.SetValue(_workspacePropertyKey, zoneWorkspace);
			zone.GotFocus += zone_GotFocus;
			return zoneWorkspace;
		}

		private static void DisownZone(ContentControl zone)
		{
			Debug.Assert(zone != null);
			zone.SetValue(_workspacePropertyKey, null);
			zone.GotFocus -= zone_GotFocus;
		}

		private static void zone_GotFocus(object sender, RoutedEventArgs e)
		{
			Debug.Assert(sender is ContentControl);
			ContentControl zone = sender as ContentControl;
			ZoneWorkspace zoneWorkspace = GetWorkspace(zone);
			Debug.Assert(zoneWorkspace != null);
			zoneWorkspace.Composer.SetActiveSmartPart(zone.Content);
		}

		private object UpdateContent(ContentControl zone, UpdateReason updateReason)
		{
			Debug.Assert(zone != null);
			object content = null;
			int smartPartIndex;

			for (smartPartIndex = InnerSmartParts.Count - 1; smartPartIndex >= 0; --smartPartIndex)
			{
				object smartPart = InnerSmartParts[smartPartIndex];

				if (object.ReferenceEquals(zone, GetZone(smartPart)))
				{
					content = smartPart;
					break;
				}
			}

			if (updateReason == UpdateReason.SmartPartClosed)
			{
				bool anotherFound = false;

				for (int j = smartPartIndex - 1; j >= 0; --j)
				{
					object smartPart = InnerSmartParts[j];

					if (object.ReferenceEquals(zone, GetZone(smartPart)))
					{
						anotherFound = true;
						break;
					}
				}

				if (!anotherFound)
				{
					content = null;
				}
			}

			zone.Content = content;
			return content;
		}

		private ContentControl GetZone(object smartPart)
		{
			Debug.Assert(_smartPartZoneNames.ContainsKey(smartPart));
			string zoneName = _smartPartZoneNames[smartPart];
			return zoneName == null ? DefaultZone : _zones[zoneName];
		}

		private void MoveSmartPartInStack(object smartPart, StackLocation newLocation)
		{
			Debug.Assert(smartPart != null);
			InnerSmartParts.Remove(smartPart);
			InnerSmartParts.Insert(newLocation == StackLocation.BottomOfStack ? 0 : InnerSmartParts.Count, smartPart);
		}

		private enum UpdateReason
		{
			Other,
			SmartPartClosed
		}

		private enum StackLocation
		{
			BottomOfStack,
			TopOfStack
		}
	}
}